﻿using System;
using System.Collections.Generic;
using System.Reflection;
using MyTestRunner.Infrastructure;

namespace MyTestRunner.Domain
{
    public class MsTestFinder : ITestFinder
    {
        private const string TestMethodAttributeName = "Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute";
        private const string SetUpAttributeName = "Microsoft.VisualStudio.TestTools.UnitTesting.TestInitializeAttribute";
        private const string TearDownAttributeName = "Microsoft.VisualStudio.TestTools.UnitTesting.TestCleanupAttribute";
        private const string ExpectedExceptionAttributeName = "Microsoft.VisualStudio.TestTools.UnitTesting.ExpectedExceptionAttribute";
        private const string ExceptionTypeProperyName = "ExceptionType";

        /// <summary>
        /// Returns all methods that are a test in mstest.
        /// </summary>
        /// <param name="assembly"></param>
        /// <returns></returns>
        public IEnumerable<MethodInfo> FindTestMethods(Assembly assembly)
        {
            return ReflectionHelper.FindPublicMethodsWithAttribute(assembly, TestMethodAttributeName);
        }

        /// <summary>
        /// Tries to get the method that should be executed before each test. 
        /// </summary>
        public bool TryGetSetUpMethod(Type testType, out MethodInfo setUpMethod)
        {
            foreach (MethodInfo method in ReflectionHelper.FindPublicMethodsWithAttribute(testType, SetUpAttributeName))
            {
                setUpMethod = method;
                return true;
            }

            setUpMethod = null;
            return false;
        }

        /// <summary>
        /// Tries to get the method that should be executed after each test. 
        /// </summary>
        public bool TryGetTearDownMethod(Type testType, out MethodInfo tearDownMethod)
        {
            foreach (MethodInfo method in ReflectionHelper.FindPublicMethodsWithAttribute(testType, TearDownAttributeName))
            {
                tearDownMethod = method;
                return true;
            }

            tearDownMethod = null;
            return false;
        }

        /// <summary>
        /// Tries to get the expected exception type.
        /// </summary>
        public bool TryGetExpectedExceptionType(MethodInfo testMethod, out Type expectedExceptionType)
        {
            foreach (Attribute attribute in testMethod.GetCustomAttributes(true))
            {
                Type attributeType = attribute.GetType();
                if (attributeType.FullName == ExpectedExceptionAttributeName)
                {
                    expectedExceptionType = (Type)attributeType.GetProperty(ExceptionTypeProperyName).GetValue(attribute, null);
                    return true;
                }
            }

            expectedExceptionType = null;
            return false;
        }
    }
}
